﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;
using Bots.BGBuddy.Helpers;
using CommonBehaviors.Actions;
using Styx;
using Styx.Common;
using Styx.CommonBot;
using Styx.CommonBot.Profiles;
using Styx.CommonBot.Routines;
using Styx.Helpers;
using Styx.Pathing;
using Styx.TreeSharp;
using Styx.WoWInternals;
using Styx.WoWInternals.WoWObjects;
using System.Windows.Media;
using Action = Styx.TreeSharp.Action;
using Timer = System.Threading.Timer;

namespace DruidBuddy
{
    public class DruidBuddy : BotBase
    {
        public override void Initialize()
        {
            Mount.OnMountUp += Mount_OnMountUp;
        }

        void Mount_OnMountUp(object sender, MountUpEventArgs e)
        {
            e.Cancel = true;
        }

        #region Properties

        private static Composite _root;

        private bool _shouldHarvest;
        public DruidBuddy()
        {
            LastIndex = 0;
        }

        #endregion

        #region Implemented Members

        public override string Name
        {
            get { return "Druid Buddy v01"; }
        }

        public override Composite Root
        {
            get { return _root ?? (_root = MainRotation()); }
        }

        public override PulseFlags PulseFlags
        {
            get { return PulseFlags.Objects; }
        }

        public int LastIndex { get; set; }

        #endregion

        #region Composites

        private Composite MainRotation()
        {
            return new PrioritySelector(
                new Decorator(ret => !StyxWoW.Me.IsAlive || StyxWoW.Me.IsGhost || StyxWoW.Me.IsDead, new Action(
                    delegate
                    {
                        GetCorpse();
                    })),
                new Decorator(ret => StyxWoW.Me.Combat,
                    new PrioritySelector(

                        #region Heal

                        new PrioritySelector(
                            // Use the Behavior
                            new Decorator(ctx => RoutineManager.Current.HealBehavior != null,
                                new Sequence(
                                    RoutineManager.Current.HealBehavior,
                                    new Action(delegate { return RunStatus.Success; })
                                    )),

                            // Don't use the Behavior
                            new Decorator(ctx => RoutineManager.Current.NeedHeal,
                                new Sequence(
                                    new Action(ret => TreeRoot.StatusText = "Healing"),
                                    new Action(ret => RoutineManager.Current.Heal())
                                    ))),

                        #endregion

                        #region Combat Buffs

                        new PrioritySelector(
                            // Use the Behavior
                            new Decorator(ctx => RoutineManager.Current.CombatBuffBehavior != null,
                                new Sequence(
                                    RoutineManager.Current.CombatBuffBehavior,
                                    new Action(delegate { return RunStatus.Success; })
                                    )
                                ),

                            // Don't use the Behavior
                            new Decorator(ctx => RoutineManager.Current.NeedCombatBuffs,
                                new Sequence(
                                    new Action(ret => TreeRoot.StatusText = "Applying Combat Buffs"),
                                    new Action(ret => RoutineManager.Current.CombatBuff())
                                    ))),

                        #endregion

                        #region Combat

                        new PrioritySelector(
                            // Use the Behavior
                            new Decorator(ctx => RoutineManager.Current.CombatBehavior != null,
                                new PrioritySelector(
                                    RoutineManager.Current.CombatBehavior,
                                    new Action(delegate { return RunStatus.Success; })
                                    )),

                            // Don't use the Behavior
                            new Sequence(
                                new Action(ret => TreeRoot.StatusText = "Combat"),
                                new Action(ret => RoutineManager.Current.Combat())))

                        #endregion

                        )),
                new Decorator(ret => StyxWoW.Me.Mounted, new Action(delegate
                {
                    Mount.Dismount();
                })),
                new Decorator(ret => _shouldHarvest, new Sequence(
                    new Action(delegate
                    {
                        Logging.Write(Colors.MediumAquamarine, "Found a node. Going to harvest.");
                        var nearbyOres =
                            ObjectManager.GetObjectsOfType<WoWGameObject>()
                                .Where(obj => obj.SubType == WoWGameObjectType.Chest &&
                                              (obj.Entry == 209311 ||
                                               obj.Entry == 209328 ||
                                               obj.Entry == 209312 ||
                                               obj.Entry == 209329 ||
                                               obj.Entry == 209313 ||
                                               obj.Entry == 209330) &&
                                              !Blacklist.Contains(obj, BlacklistFlags.Node))
                                .OrderBy(obj => obj.Location.Distance(StyxWoW.Me.Location))
                                .ToList();

                        var nearbyHerbs =
                            ObjectManager.GetObjectsOfType<WoWGameObject>()
                                .Where(obj => obj.Entry == 209354 && !Blacklist.Contains(obj, BlacklistFlags.Node))
                                .OrderBy(obj => obj.Location.Distance(StyxWoW.Me.Location))
                                .ToList();

                        if (nearbyOres.Any())
                        {
                            foreach (var source in nearbyOres.Where(s => !Blacklist.Contains(s, BlacklistFlags.Node)))
                            {
                                if (GetInCombat()) break;
                                if (!Goto(source)) return;
                                Thread.Sleep(TimeSpan.FromSeconds(1));
                                Logging.Write(Colors.MediumAquamarine, "Harvesting {0}", source.Name);
                                source.Interact();
                                Thread.Sleep(TimeSpan.FromSeconds(5));
                            }
                            ObjectManager.Update();
                        }

                        if (nearbyHerbs.Any())
                        {
                            foreach (var source in nearbyHerbs.Where(s => !Blacklist.Contains(s, BlacklistFlags.Node)))
                            {
                                if (GetInCombat()) break;
                                if (!Goto(source)) return;
                                Thread.Sleep(TimeSpan.FromSeconds(1));
                                Logging.Write(Colors.MediumAquamarine, "Harvesting {0}", source.Name);
                                source.Interact();
                                Thread.Sleep(TimeSpan.FromSeconds(2));
                            }
                            ObjectManager.Update();
                        }

                        _shouldHarvest = false;
                    })
                    )),
                new Decorator(ret => !StyxWoW.Me.Mounted, new Action(delegate
                {
                    var closestHotspot =
                        ProfileManager.CurrentProfile.HotspotManager.Hotspots.OrderBy(
                            x => StyxWoW.Me.Location.Distance(x)).FirstOrDefault();

                    for (var i = 0; i < ProfileManager.CurrentProfile.HotspotManager.Hotspots.Count; i++)
                    {
                        if (ProfileManager.CurrentProfile.HotspotManager.Hotspots[i] == closestHotspot)
                        {
                            LastIndex = i + 1;
                            break;
                        }
                    }
                    
                    if (LastIndex == ProfileManager.CurrentProfile.HotspotManager.Hotspots.Count) LastIndex = 0;

                    for (var i = LastIndex; i < ProfileManager.CurrentProfile.HotspotManager.Hotspots.Count; i++)
                    {
                        ObjectManager.Update();
                        var ores =
                            ObjectManager.GetObjectsOfType<WoWGameObject>()
                                .Where(obj => obj.SubType == WoWGameObjectType.Chest &&
                                              (obj.Entry == 209311 ||
                                               obj.Entry == 209328 ||
                                               obj.Entry == 209312 ||
                                               obj.Entry == 209329 ||
                                               obj.Entry == 209313 ||
                                               obj.Entry == 209330) &&
                                              !Blacklist.Contains(obj, BlacklistFlags.Node)).ToList();

                        var herbs =
                            ObjectManager.GetObjectsOfType<WoWGameObject>()
                                .Where(obj => obj.Entry == 209354 && !Blacklist.Contains(obj, BlacklistFlags.Node))
                                .ToList();

                        if (GetInCombat()) break;

                        if (ores.Any() || herbs.Any())
                        {
                            _shouldHarvest = true;
                            LastIndex = i;
                            break;
                        }

                        Goto(ProfileManager.CurrentProfile.HotspotManager.Hotspots[i]);
                    }
                }))
                );
        }



        #endregion

        #region Helper Methods

        public static bool Goto(WoWGameObject obj)
        {
            var rnd = new Random();
            var randomDistance = rnd.Next(1, 3);
            var nearestLocationToObject = WoWMathHelper.CalculatePointFrom(StyxWoW.Me.Location, obj.Location, randomDistance);
            var pathToDest = Navigator.GeneratePath(StyxWoW.Me.Location, nearestLocationToObject);
            
            foreach (var localDest in pathToDest)
            {
                var w = new Stopwatch();
                w.Start();
                try
                {
                    while (localDest.Distance(StyxWoW.Me.Location) >= obj.InteractRange && w.Elapsed <= TimeSpan.FromSeconds(60))
                    {
                        if (GetInCombat()) break;
                        PreventPlayersOrMobs();

                        if (w.Elapsed >= TimeSpan.FromSeconds(60))
                        {
                            AddToBlackList(obj);
                            w.Stop();
                            return false;
                        }

                        Navigator.MoveTo(localDest);
                        Thread.Sleep(100);
                    }

                    if (GetInCombat()) break;
                }
                catch(Exception exception)
                {
                    Logging.Write(Colors.Red, exception.Message);
                    AddToBlackList(obj);
                    break;
                }
                
            }

            return true;
        }

        public static bool Goto(WoWPoint location, int distance)
        {

            var nearestLocationToObject = WoWMathHelper.CalculatePointFrom(StyxWoW.Me.Location, location, distance);
            var pathToDest = Navigator.GeneratePath(StyxWoW.Me.Location, nearestLocationToObject);

            foreach (var localDest in pathToDest)
            {
                var w = new Stopwatch();
                w.Start();

                try
                {
                    while (localDest.Distance(StyxWoW.Me.Location) >= 5 && w.Elapsed <= TimeSpan.FromSeconds(60))
                    {
                        if (GetInCombat()) break;
                        PreventPlayersOrMobs();

                        if (w.Elapsed >= TimeSpan.FromSeconds(60))
                        {
                            w.Stop();
                            return false;
                        }

                        Navigator.MoveTo(localDest);
                        Thread.Sleep(100);
                    }
                    if (GetInCombat()) break;
                }
                catch (Exception exception)
                {
                    Logging.Write(Colors.Red, exception.Message);
                }
            }

            return true;
        }

        public static bool Goto(WoWPoint location)
        {
            var rnd = new Random();
            var randomDistance = rnd.Next(1, 3);
            return Goto(location, randomDistance);
        }

        private static void AddToBlackList(WoWGameObject obj)
        {
            Blacklist.Add(obj, BlacklistFlags.Node, TimeSpan.FromSeconds(60));
            Logger.Write(Colors.Red, "Cant reach node. Blacklisting for 60 seconds");
        }

        public static bool GetCorpse()
        {
            var result = Goto(StyxWoW.Me.CorpsePoint, 10);
            if (result)
            {
                Thread.Sleep(TimeSpan.FromSeconds(3));
                Lua.DoString("RetrieveCorpse()");
            }

            return result;
        }

        private static void PreventPlayersOrMobs()
        {
            ObjectManager.Update();

            var nearbyPlayer =
                ObjectManager.GetObjectsOfType<WoWPlayer>()
                .Where(x => x.Distance < 99)
                    .OrderBy(x => StyxWoW.Me.Location.Distance(x.Location))
                    .FirstOrDefault();

            var nearbyMob = (from mob in ObjectManager.GetObjectsOfType<WoWUnit>()
                where mob.IsHostile && mob.Distance < 20
                select mob).FirstOrDefault();
            
            if (StyxWoW.Me.HasAura("Prowl") && (nearbyPlayer != null || nearbyMob != null)) return;

            if (nearbyPlayer != null)
            {
                if (!StyxWoW.Me.IsStealthed && SpellManager.CanCast("Prowl"))
                {
                    Logging.Write(Colors.SkyBlue,
                        "Detected nearby player: {0} Level: {1} Class: {2} Distance {3}. Going stealth.",
                        nearbyPlayer.Name, nearbyPlayer.Level, nearbyPlayer.Class,
                        StyxWoW.Me.Location.Distance(nearbyPlayer.Location));

                    SpellManager.Cast("Prowl");
                }
            }
            else if (nearbyMob != null)
            {
                if (!StyxWoW.Me.IsStealthed && SpellManager.CanCast("Prowl"))
                {
                    Logging.Write(Colors.SkyBlue,
                        "Detected nearby mob: {0} Level: {1} Distance {2}. Going stealth.",
                        nearbyMob.Name, nearbyMob.Level,
                        StyxWoW.Me.Location.Distance(nearbyMob.Location));

                    SpellManager.Cast("Prowl");
                }
            }
            else if (StyxWoW.Me.IsSwimming)
            {
                if (!StyxWoW.Me.HasAura("Aquatic Form"))
                {
                    SpellManager.Cast("Aquatic Form");
                }
            }
            else
            {
                if (!StyxWoW.Me.HasAura("Travel Form"))
                {
                    SpellManager.CastSpellById(783);
                }
            }
        }

        private static bool GetInCombat()
        {
            if (StyxWoW.Me.Combat)
            {
                var nearbyMobs =
                    ObjectManager.GetObjectsOfType<WoWUnit>()
                        .Where(obj => obj.Aggro && obj.Attackable && !obj.Elite && obj.IsHostile).ToList();

                SpellManager.Cast(nearbyMobs.Count >= 3 ? "Bear Form" : "Cat Form");

                return true;
            }
            return false;
        }


        #endregion
    }
}
